package io.github.uicdb.badskill;

import com.mojang.logging.LogUtils;
import io.github.uicdb.badskill.api.CostumerConsumer;
import io.github.uicdb.badskill.api.CostumerConsumerManager;
import io.github.uicdb.badskill.api.RegisterConsumerEvent;
import io.github.uicdb.badskill.api.impl.ScoreBoardConsumer;
import io.github.uicdb.badskill.network.*;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameRules;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.common.util.NonNullConsumer;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.OnDatapackSyncEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.network.PacketDistributor;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;

import java.util.function.Supplier;

// The value here should match an entry in the META-INF/mods.toml file
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
@Mod(BadSkill.MODID)
public class BadSkill {

    // Define mod id in a common place for everything to reference
    public static final String MODID = "badskill";
    // Directly reference a slf4j logger
    private static final Logger LOGGER = LogUtils.getLogger();
    private static GameRules.Key<GameRules.BooleanValue> RULE_KEEP_SKILL;

    public BadSkill() {
        MinecraftForge.EVENT_BUS.register(this);
        NetWorking.registerMessage();
        BSRegistries.register(FMLJavaModLoadingContext.get().getModEventBus());
        BSConfig.registerConfig();

    }

    @SubscribeEvent
    public void onReloadData(AddReloadListenerEvent event) {
        event.addListener(SkillPageManager.INSTANCE);
        event.addListener(SkillOptionManager.INSTANCE);
    }

    @SubscribeEvent
    public void onDataSync(OnDatapackSyncEvent event) {
        if (event.getPlayer() != null) {
            syncPlayerPackage(event::getPlayer);
        } else
            event.getPlayerList().getPlayers().forEach(serverPlayer -> {
                syncPlayerPackage(() -> serverPlayer);
            });
    }

    @SubscribeEvent
    public void onPlayerClone(PlayerEvent.Clone event) {
        Player original = event.getOriginal();
        Player newOne = event.getEntity();
        boolean keep = original.level().getGameRules().getBoolean(RULE_KEEP_SKILL);
        System.out.println("keep = " + keep);
        original.reviveCaps();
        if (event.isWasDeath()) {
            if (keep) {
                copyCapability(PlayerSkillCapabilityProvider.CAPABILITY, original, newOne);
                copyCapability(OnlyCapabilityProvider.CAPABILITY, original, newOne);
            }
        } else {
            copyCapability(PlayerSkillCapabilityProvider.CAPABILITY, original, newOne);
            copyCapability(OnlyCapabilityProvider.CAPABILITY, original, newOne);
        }
        original.invalidateCaps();
    }

    @SubscribeEvent
    public void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
        if(event.getEntity() instanceof ServerPlayer serverPlayer){
            serverPlayer.getCapability(PlayerSkillCapabilityProvider.CAPABILITY).ifPresent(PlayerSkillCapability::afterCopyCapability);
            serverPlayer.getCapability(OnlyCapabilityProvider.CAPABILITY).ifPresent(OnlyCapability::afterCopyCapability);
        }

    }

    @SuppressWarnings("all")
    private <V extends Tag, T extends INBTSerializable<V>> void copyCapability(Capability<T> capability, Player from, Player to) {
        from.getCapability(capability)
                .ifPresent(cap -> {
                    to.getCapability(capability)
                            .ifPresent(p -> {
                                        if (cap instanceof ICapabilityCoppier coper) {
                                            coper.beforeCopyCapability();
                                        }
                                        p.deserializeNBT(cap.serializeNBT());
                                    }
                            );
                });
    }

    private void syncPlayerPackage(Supplier<ServerPlayer> playerSupplier) {
        ServerPlayer player = playerSupplier.get();
        NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(playerSupplier), SkillPageSyncPackage.create());
        NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(playerSupplier), SkillOptionSyncPackage.create());
        player.getCapability(PlayerSkillCapabilityProvider.CAPABILITY)
                .ifPresent(capability -> SkillOptionManager.INSTANCE.exceptionsOptions.forEach((resourceLocation, option) -> {
                    capability.retreated(option, false);
                }));
        NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(playerSupplier), PlayerLearnedSyncPackage.create(player));
        player.getCapability(OnlyCapabilityProvider.CAPABILITY).ifPresent(onlyCapability -> NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> player),new AllOnlySyncPackage(onlyCapability.getExistKeys())));

    }

    @SubscribeEvent
    public static void onCommonSetup(FMLCommonSetupEvent event) {
        event.enqueueWork(() -> {
            LOGGER.debug("Start register consumers");
            ModLoader.get().runEventGenerator(RegisterConsumerEvent::new);
            LOGGER.debug("Finished register consumers,Total is {}", CostumerConsumerManager.size());
            RULE_KEEP_SKILL = GameRules.register("keepSkill", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true));
        });
    }

    @SubscribeEvent
    public static void onRegisterConsumer(RegisterConsumerEvent event) {
        event.register("empty", p -> CostumerConsumer.EMPTY);
        event.register("score", p -> ScoreBoardConsumer.INSTANCE);
        event.register("only", p -> p.getCapability(OnlyCapabilityProvider.CAPABILITY).resolve().orElseThrow());
    }

    @SubscribeEvent
    public static void onDataGen(GatherDataEvent event) {
        event.getGenerator().addProvider(event.includeServer(), new SkillDumper(event.getGenerator()));
        event.getGenerator().addProvider(event.includeServer(), new PageDumper(event.getGenerator()));
    }

    @SubscribeEvent
    public void onAttachCapability(AttachCapabilitiesEvent<Entity> event) {
        if (event.getObject() instanceof Player player) {
            event.addCapability(new ResourceLocation(MODID, MODID), new PlayerSkillCapabilityProvider(player));
            event.addCapability(new ResourceLocation(MODID, "only"), new OnlyCapabilityProvider(player));
        }
    }


}
